/*
 * @features: VM核心
 * @description: watchPath 的基础，只提供绑定功能 和 对应的数据更新函数
 * @Date: 2021-09-06 11:47:17
 * @Author: judu233(769471424@qq.com)
 * @LastEditTime: 2021-10-05 11:14:23
 * @LastEditors: judu233
 */
import { EDITOR } from 'cc/env';
import c3d from '../../../cc';
import VmEvent, { ValueChangeCall } from './VmEvent';
import VmFastCall from './VmFastCall';
import VmFind from './VmFind';
import VmWatch from './VmWatch';
import { ProxyOb } from './ProxyOb';
const { ccclass, property, executeInEditMode, disallowMultiple, menu } = c3d._decorator;

/**自动检查识别的数组,你可以准备自己的组件放上去自动识别 ,系统组件只能是实例，自定义的可以是字符串*/
export const COMP_MAP_ARR: Map<string, [any, string]> = new Map<string, [any, string]>([
    [`Label`, [c3d.Label, `string`]],
    [`RichText`, [c3d.RichText, `string`]],
    [`EditBox`, [c3d.EditBox, `string`]],
    [`Slider`, [c3d.Slider, `progress`]],
    [`ProgressBar`, [c3d.ProgressBar, `progress`]],
    [`Toggle`, [c3d.Toggle, `isChecked`]],
    [`VmToggleGroup`, [`VmToggleGroup`, `_checkIndex`]],
    [`VmSprite`, [`VmSprite`, `_index`]],
    [`VmPage`, [`VmPage`, `_index`]],
]);


@ccclass
@executeInEditMode(true)
@disallowMultiple(true)
@menu(`VM/VM-Base`)
export default class VmBase extends c3d.Component {
    @property(VmWatch)
    watch: VmWatch = new VmWatch();

    @property(VmEvent)
    event: VmEvent = new VmEvent();

    @property(VmFind)
    find: VmFind = new VmFind();

    @property(VmFastCall)
    eFast: VmFastCall = new VmFastCall();

    /**组件代理者 */
    proxyOb: ProxyOb<c3d.Component>;

    /**代理的实体组件 比如Label,直接操控可以更改数据模型， 直接使用 proxy.string = xxx*/
    proxy: ProxyOb<c3d.Component>[`proxy`];

    //#region 生命周期
    onLoad() {
        if (EDITOR) {
            //只在运行时检查组件是否缺失可用
            this.onEditor();
        } else {
            if (this.checkCanWatch()) {
                this.onDataInit();
            }
        }
    }
    start() { }

    /**运行时检查组件是否缺失可用 */
    onEditor() {
        /**挂在对应节点后，自动获取组件属性和名字, 会查找最排前符合的第一个组件 */
        for (let com of this.node.components) {
            let comName = com.constructor.name;
            c3d.log(comName)
            let params = COMP_MAP_ARR.get(comName);
            if (params) {
                if (this.watch.componentName == ``)
                    this.watch.componentName = comName;
                if (this.watch.componentProperty == ``)
                    this.watch.componentProperty = params[1];
                // if (params[2] !== null)
                //     this.controller = params[2] as boolean;
                return;
            }
        }
        this.watch.componentName = this.watch.componentProperty = ``;
    }

    onDestroy() {
        this.onDataDestroy();
    }

    onEnable() {
        this.onDataEnable();
    }

    onDisable() {
        this.onDataDisEnable();
    }

    onRestore() {
        if (EDITOR) {
            this.onEditor();
        }
    }

    /**虚方法，脚本初始化调用 */
    protected onDataInit() {
        this.setComponentValue(ProxyOb.getValue(this.watch.path));
    }

    /**虚方法，值启用时调用 */
    protected onDataEnable() {
        if (!EDITOR && this.watch._canWatchData)
            this.setMultPathEvent(true);
    }

    /**虚方法，值禁用时调用 */
    protected onDataDisEnable() {
        if (!EDITOR && this.watch._canWatchData)
            this.setMultPathEvent(false);
    }

    /**虚方法，值销毁时调用 */
    protected onDataDestroy() { }

    /**虚方法，监听的数值改变后回调 */
    protected onDataChange(newValue, oldValue, path: string) {
        this.setComponentValue(newValue);
        this.onChangeCall(false, newValue, oldValue, path);
    }

    /**虚方法，监听的组件值发生变化后，触发更新此值 */
    protected onComponentChange(newValue, oldValue, path: string) {
        ProxyOb.setValue(this.watch.path, newValue);
        this.onChangeCall(true, newValue, oldValue, path);
    }

    /**触发修改后的回调 */
    protected onChangeCall(isComCall = true, newValue, oldValue, path: string) {
        let change: ValueChangeCall;
        if (this.event.isComCall && isComCall) {
            change = this.event.comChangeCall;
            this.event.isComCall = change.valueTriggerMore;
        } else if (this.event.isValueCall && !isComCall) {
            change = this.event.valueChangeCall;
            this.event.isValueCall = change.valueTriggerMore;
        }
        if (change) {
            if (!change.valueConditionCheck(newValue, change.valueCompare))
                return;
            change.valueChangeEvents.forEach(v => {
                v.call([newValue, oldValue, path]);
            });
        }
    }
    //#endregion

    //#region ! 快速修改值，快速给button添加修改数值事件使用
    A_vAddInt(e, data) {
        this.eFast.add(e, data, this.watch.path, true);
    }
    A_vSubInt(e, data) {
        this.eFast.sub(e, data, this.watch.path, true)
    }
    A_vMulInt(e, data) {
        this.eFast.mul(e, data, this.watch.path, true)
    }
    A_vDivInt(e, data) {
        this.eFast.div(e, data, this.watch.path, true)
    }
    A_vAdd(e, data) {
        this.eFast.add(e, data, this.watch.path, false);
    }
    A_vSub(e, data) {
        this.eFast.sub(e, data, this.watch.path, false)
    }
    A_vMul(e, data) {
        this.eFast.mul(e, data, this.watch.path, false)
    }
    A_vDiv(e, data) {
        this.eFast.div(e, data, this.watch.path, false)
    }
    A_vSetString(e, data) {
        ProxyOb.setValue(this.watch.path, data);
    }
    A_vSetNumberInt(e, data) {
        this.eFast.setNum(e, data, this.watch.path, true);
    }
    A_vNumber(e, data) {
        this.eFast.setNum(e, data, this.watch.path, false);
    }
    A_vSetStringByCustomData(e, data, customData) {
        ProxyOb.setValue(this.watch.path, customData);
    }

    //#endregion

    //#region 组件的监听
    /**检查是否可以监听组件 */
    checkCanWatch() {
        //检查观测数据路径
        if (!this._checkDataPath()) return false;
        //解析路径
        this.parseDataPath();

        //绑定检查观察组件
        if (!this._checkComponent()) return false;
        this._bindComponentProxy();
        return true;
    }

    /**检查填写的观察路径 */
    _checkDataPath() {
        if (this.watch.useMultipathMode)
            this.watch._canWatchData = !this.watch.pathArr.some(path => path == "")
        else
            this.watch._canWatchData = this.watch.path != ``;

        if (!this.watch._canWatchData) {
            c3d.warn(`节点${this.node.name}上VM组件没有填写完整监听数据的路径`);
            return false;
        }
        return true;
    }

    /**检查观察组件 */
    _checkComponent() {
        let arr = COMP_MAP_ARR.get(this.watch.componentName);
        this.watch._watchComponent = this.getComponent(arr[0]);
        if (!this.watch._watchComponent || !this.watch.componentProperty) {
            c3d.warn(`节点${this.node.name}未设置需要监听的组件/属性`);
            return false;
        }
        if (this.watch.componentProperty in this.watch._watchComponent === false) {
            c3d.warn(`节点${this.node.name}需要监听的组件的属性${this.watch.componentProperty}不存在`);
            return false;
        }
        if (!this.watch._watchComponent.enabled)
            return false;
        return true;
    }

    /**解析路径 */
    parseDataPath() {
        /**解析子路径 */
        let _parseTransChildPath = (paths: string[]) => {
            for (let i = 1; i < paths.length; i++) {
                let p = paths[i];
                //如果发现了路径使用了 * ，则自动去自己的父节点查找自己所在 index 值
                if (p == `*`) {
                    let index = this.node.parent.children.findIndex(n => n === this.node);
                    if (index <= 0) index = 0;
                    paths[i] = index.toString();
                    break;
                }
            }
        };
        //提前拆分、并且解析路径
        let paths = this.watch.path.split(`.`);
        _parseTransChildPath(paths);
        //替换掉原路径
        this.watch.path = paths.join(`.`);
        //提前进行路径数组 的 解析
        let pathArr = this.watch.pathArr;
        if (pathArr.length >= 1) {
            for (let i = 0; i < pathArr.length; i++) {
                let paths = pathArr[i].split(`.`);
                _parseTransChildPath(paths);
                this.watch.pathArr[i] = paths.join(`.`);
            }
        }
        //打印出所有绑定的路径，方便调试信息
        if (EDITOR)
            c3d.log(`所有路径`, this.watch.path ? [this.watch.path] : this.watch.pathArr, `<<`, this.node.getParent().name + `.` + this.node.name)
        if (this.watch.path == `` && this.watch.pathArr.join(``) == ``)
            c3d.log(`可能未设置路径的节点:`, this.node.getParent().name + `.` + this.node.name);
    }

    /**设置多路径监听开启和关闭 */
    setMultPathEvent(enabled = true) {
        if (this.watch.useMultipathMode) {
            this.watch.pathArr.forEach(path => path != `` && this._setDataProxy(path, enabled));
        } else if (this.watch.path != ``) {
            this._setDataProxy(this.watch.path, enabled);
        }
    }

    /**注册路径监听 */
    _setDataProxy(path: string, enabled: boolean) {
        if (enabled) ProxyOb.bindPath(`DataProxy_`, path, this.onDataChange, this);
        else ProxyOb.unBindPath(`DataProxy_`, path, this.onDataChange, this);
    }

    /**绑定组件监听代理 */
    _bindComponentProxy() {
        if (this.watch.controller) {
            //注册组件监听，只监听一层,如果有特殊要求可以重写监听,如果监听组件开启了深遍历，会有额外的触发，导致监听无效或参数错误
            this.proxyOb = new ProxyOb(this.watch._watchComponent, this._getOnComWatchHead(), this.watch._controllerIsWatchDeep);
            this.proxyOb.proxyValueChangeCall = this.bindComPropertyChange.bind(this);
            this.proxy = this.proxyOb.proxy;
            this.watch._canWatchComponent = true;
        }
    }

    /**获取组件的监听的head(不是数据域) */
    _getOnComWatchHead() {
        return this.watch._watchComponent.uuid + this.watch.componentProperty;
    }

    /**监听的属性发生变动 （组件属性）*/
    bindComPropertyChange(newValue: any, oldValue: any, key: string, receiver) {
        this.onComponentChange(newValue, oldValue, this.watch.path);
    }

    /**获取监听组件中指定的属性 */
    getComponentValue() {
        return this.watch._watchComponent[this.watch.componentProperty];
    }

    /**设置监听组件中指定的属性 */
    setComponentValue(value: any) {
        //如果要设置的值是数字，转化成字符，不然为0时不会显示
        this.watch._watchComponent[this.watch.componentProperty] = value.toString();
    }
    //#endregion

}

/**
 * 数值格式化函数, 通过语义解析自动设置值的范围
 * 1:def(0)//显示一个默认值
 * 
//  */
// export class StringFormat {
//     /**
//      * 根据指定格式解析数值
//      * @param value 值
//      * @param format 格式
//      * @returns 数值
//      * @example
//      *  组件实际使用 后面跟数字表示限制参数
//      *  {{0:sep}}将数字按分号显示   100000-> 100,000 
//      *  {{0:per2}} 将取值0~1 变成 1~100,可以指定修饰的小数位数  
//      *  {{0:int}} 将取值变成整数 
//      *  {{0:fix2}}数值转换为小数
//      *  {{0:limit3}}字符串长度限制 
//      *  使用{{1:int}} 表示替换组件绑定的路径中第一个的数据模型 并限制为int整数
//      */
//     static deal(value: number | string, format: string): string {
//         if (format === ``) return value as string;
//         format = format.toLowerCase().trim();//不区分大小
//         let match_func = format.match(/^[a-z|A-Z]+/gi);//匹配到 format 中的 函数名
//         let match_num = format.match(/\d+$/gi);   //匹配到 format 中的参数
//         let func: string = ``;
//         let num: number;
//         let res: number | string = ``;

//         if (match_func) func = match_func[0];
//         if (match_num) num = parseInt(match_num[0]);
//         if (typeof value == `number`) {
//             switch (func) {
//                 case `int`: res = this.int(value); break;
//                 case `fix`: res = this.fix(value, num); break;
//                 case `kmbt`: res = this.KMBT(value); break;
//                 case `per`: res = this.per(value, num); break;
//                 case `sep`: res = this.sep(value); break;
//                 default: break;
//             }
//         } else {
//             switch (func) {
//                 case `limit`: res = this.limit(value, num); break;
//                 default: break;
//             }
//             // res = value;
//         }
//         return res as string;
//     }

//     /**[value:sep]将数字按分号显示 100000-> 100,000 */
//     static sep(value: number) {
//         let num = Math.round(value).toString();
//         return num.replace(new RegExp(`(\\d)(?=(\\d{3})+$)`, `ig`), "$1,");
//     }

//     /** [value:per2] 将取值0~1 变成 1~100,可以指定修饰的小数位数 */
//     static per(value: number, fd: number) {
//         return Math.round(value * 100).toFixed(fd);
//     }

//     /** [value:int] 将取值变成整数 */
//     static int(value: number) {
//         return Math.round(value);
//     }

//     /** [value:fix2]数值转换为小数*/
//     static fix(value: number, fd: number) {
//         return value.toFixed(fd)
//     }

//     /** [value:limit3]字符串长度限制 */
//     static limit(value: string, count: number) {
//         return value.substring(0, count);
//     }

//     /** [value:kmbt]将数字缩短显示为KMBT单位 大写,目前只支持英文 */
//     static KMBT(value: number, lang: string = `en`) {
//         //10^4=万, 10^8=亿,10^12=兆,10^16=京，
//         let counts = [1000, 1000000, 1000000000, 1000000000000];
//         let units = [``, `K`, `M`, `B`, `T`];

//         switch (lang) {
//             case `zh`:
//                 //10^4=万, 10^8=亿,10^12=兆,10^16=京，
//                 counts = [10000, 100000000, 1000000000000, 10000000000000000];
//                 units = [``, `万`, `亿`, `兆`, `京`];
//                 break;
//             default:
//                 break;
//         }
//         return this.compressUnit(value, counts, units, 2);
//     }

//     //压缩任意单位的数字，后缀加上单位文字
//     static compressUnit(value, counts: number[], units: string[], fixNum: number = 2): string {
//         let res: string;
//         let index = 0;
//         for (; index < counts.length; index++) {
//             if (value < counts[index]) {
//                 if (index > 0) {
//                     res = (value / counts[index - 1]).toFixed(fixNum);
//                 } else {
//                     res = value.toFixed(0);
//                 }
//                 break;
//             }
//         }
//         return res + units[index];
//     }

//     //将数字按分显示 00:00 显示 （ms制）
//     static time_m(value: number) {
//         //todo
//     }

//     //将数字按秒显示 00:00:00 显示 （ms制）
//     static time_s(value: number) {
//         //todo
//     }

//     //将数字按 0:00:00:000 显示 （ms制）
//     static time_ms(value: number) {
//         //todo
//     }

//     //将时间戳显示为详细的内容
//     static timeStamp(value: number) {
//         //todo
//         return new Date(value).toString()
//     }

// }


/** !! 修改的enum样式是个引用对象，一改，其他脚本也会更改 */
/**编辑面板属性刷新 */
/**获取观察数据的属性列表 */
// getWatchPropertyList() {
//     if (this.data)
//         this.dataKV = Object.keys(this.data);
//     let obj = {};
//     this.dataKV.forEach((v, i) => obj[v] = i + 1);
//     return obj;
// }

//     private _editorRefresh() {
//     let e = defaultEnum;
//     if (this.dataObj) {
//         //动态刷新替换enum属性
//         let v = this.dataObj.getWatchPropertyList();
//         if (v) {
//             v[`None`] = 0;
//             e = Enum(v) as any;
//         }
//     }

    // Class.Attr.setClassAttr(this, `watchObj`, `enumList`, Enum.getList(e));
//     Class.Attr.getClassAttrs(this)[`__proto__`][`_watchObj$_$enumList`] = Enum.getList(e);
    // watchObj[`__proto__`][`constructor`][`__attrs__`][`watchProperty$_$enumList`] = Enum.getList(e);
//     Editor.Utils.refreshSelectedInspector(`node`, this.node.uuid);
// }
